Android应用管理二 --APK包的安装、卸载和优化(PackageManagerService)

继续上一章讲解。

在Android中,通过发送Intent,就可以启动应用的安装过程,如下所示:

Uri uri = Uri.fromFile(new File(fileName));
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri, "application/vnd.android.package-archive");
startActivity(intent);
在Android的系统应用PackageInstaller中有一个PackageInstallerActivity会响应这个Intent。在这个Activity中,有两个重要的成员变量mPm,是ApplicationPackageManager的实例对象,也是PackageManagerService在应用中的代理对象。创建代码如下:

mPm = getPackageManager();
一、管理“安装会话”----PackageInstallerService

PackageInstallerService是Android5.0新加入的服务,主要用于管理“安装会话(Installer Session)”。在Android5.0中,可以通过PackageInstallerService来分配一个SessionId,这个系统唯一的ID代表一次安装过程,如果一个应用的安装必须分成几个阶段来完成,即使设备重启了,也可以通过这个ID来继续安装过程。

PackageInstallerService中提供了接口createSession()创建一个Session:

    public int createSession(SessionParams params, String installerPackageName, int userId) {
        try {
            return createSessionInternal(params, installerPackageName, userId);
        } catch (IOException e) {
            throw ExceptionUtils.wrap(e);
        }
    }
createSession方法将返回一个系统唯一值作为SessionID。如果希望再次使用这个Session,可以通过接口openSession打开它,代码如下:

    public IPackageInstallerSession openSession(int sessionId) {
        try {
            return openSessionInternal(sessionId);
        } catch (IOException e) {
            throw ExceptionUtils.wrap(e);
        }
    }

    private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException {
        synchronized (mSessions) {
            final PackageInstallerSession session = mSessions.get(sessionId);
            if (session == null || !isCallingUidOwner(session)) {
                throw new SecurityException("Caller has no access to session " + sessionId);
            }
            session.open();
            return session;
        }
    }
openSession方法返回一个IPackageInstallerSession对象,它是Binder服务PackageInstallerSession的IBinder对象。在PackageInstallerService中mSessions数组保存了所有PackageInstallerSession对象,代码如下:

private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
当系统启动时,PackageManagerService初始化时会创建PackageInstallerService服务,在这个服务的初始化函数中,会读取/data/system目录下install_sessions.xml文件,这个文件中保存了系统中未完成的Install Session。PackageInstallerService会根据文件的内容创建PackageInstallerSession对象并插入到mSessions中。

PacakgeInstallerSession中保存了应用安装相关的数据,如,安装包的路径、安装的进度、中间数据保存的目录等。

二、应用安装第一阶段:复制文件

应用中可以调用PackageManager的installPackage()方法来开始安装过程,这个方法会调用PackageManagerService的installPackage()方法或installPackageAsUser()接口来执行安装过程。整个过程比较复杂,我们先看看安装序列图,如下:

应用安装的过程大概分成两个阶段,第一阶段把需要安装的应用复制到/data/app目录下,第二阶段是对apk文件进行扫描优化,然后装载到内存中。

PackageManagerService的installPackage方法用来给当前用户安装应用,而installPackageAsUser()方法给指定的用户安装应用。installPackage()方法只是使用当前用户的uid来调用installPackageAsUser()方法。代码如下:

    @Override
    public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
            int installFlags, String installerPackageName, VerificationParams verificationParams,
            String packageAbiOverride, int userId) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);//检查调用进程的权限

        final int callingUid = Binder.getCallingUid();//检查调用进程的用户是否有权限安装应用
        enforceCrossUserPermission(callingUid, userId, true, true, "installPackageAsUser");

        if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {//检查指定的用户是否被限制安装应用
            try {
                if (observer != null) {
                    observer.onPackageInstalled("", INSTALL_FAILED_USER_RESTRICTED, null, null);
                }
            } catch (RemoteException re) {
            }
            return;
        }

        if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
            installFlags |= PackageManager.INSTALL_FROM_ADB;//调用进程uid为0或2000,设置installFlags

        } else {
            // Caller holds INSTALL_PACKAGES permission, so we're less strict
            // about installerPackageName.

            installFlags &= ~PackageManager.INSTALL_FROM_ADB;
            installFlags &= ~PackageManager.INSTALL_ALL_USERS;
        }

        UserHandle user;
        if ((installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {//如果installFlags带有标记INSTALL_ALL_USERS,则给所有用户安装
            user = UserHandle.ALL;
        } else {
            user = new UserHandle(userId);
        }

        verificationParams.setInstallerUid(callingUid);

        final File originFile = new File(originPath);
        final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile);
        //保存参数到InstallParams对象并发送消息
        final Message msg = mHandler.obtainMessage(INIT_COPY);
        msg.obj = new InstallParams(origin, observer, installFlags,
                installerPackageName, verificationParams, user, packageAbiOverride);
        mHandler.sendMessage(msg);
    }

installPackageAsUser()方法首先检查调用进程是否有安装应用的权限,再检查调用进程的所属用户是否有权限安装应用,

最后检查指定的用户是否被限制安装应用。如果参数installFlags带有标记INSTALL_ALL_USERS,则该应用将给系统中

所有用户安装,否则只给指定的用户安装。

installPackageAsUser()方法,把调用的参数保存在InstallParams对象中,然后发送INIT_COPY消息。

InstallParams是安装过程中主要的数据结构,从HandleParams类派生,用来记录安装应用的参数

从HandleParams派生的还有MoveParams和MeasureParams,MoveParams用于将应用移动到SD卡,

MeasureParams用于方法getPackageSizeInfo中。InstallParams成员变量mArgs,是FileInstallArgs类型,用来执行

apk文件的复制。

我们看下INIT_COPY消息的处理,在doHandleMessage()方法中,如下:

                case INIT_COPY: {
                    HandlerParams params = (HandlerParams) msg.obj;
                    int idx = mPendingInstalls.size();
                    if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
                    // If a bind was already initiated we dont really
                    // need to do anything. The pending install
                    // will be processed later on.
                    if (!mBound) {
                        // If this is the only one pending we might
                        // have to bind to the service again.
                        if (!connectToService()) {//绑定DefaultContainerService服务
                            Slog.e(TAG, "Failed to bind to media container service");
                            params.serviceError();
                            return;//连接服务失败,退出
                        } else {
                            // Once we bind to the service, the first连接成功,把安装信息保存到mPedingInstalls中
                            // pending request will be processed.
                            mPendingInstalls.add(idx, params);//待收到连接的返回消息后再继续安装
                        }
                    } else {
                        mPendingInstalls.add(idx, params);//插入安装信息
                        // Already bound to the service. Just make
                        // sure we trigger off processing the first request.
                        if (idx == 0) {//如果mPendingInstalls只有一项,立刻发MCS_BOUND消息
                            mHandler.sendEmptyMessage(MCS_BOUND);
                        }
                    }
                    break;
                }
在INIT_COPY消息的处理中 将绑定DefaultContainerService,因为这是一个异步的过程,要等待绑定的结果通过onServiceConnected()返回,所以,这里将安装的参数信息放到mPendingInstalls列表中。如果这个service以前就绑定好了,现在不在需要再绑定,安装信息也会先放到mPendingInstalls中。如果有多个安装请求同时到达,通过mPendingInstalls列表可以对它们进行排队。如果列表中只有一项,说明没有更多的安装请求,此时会立即 发送MCS_BOUND消息,进入下一步的处理。而onServiceConnected()方法的处理同样是发送MCS_BOUND消息,如下:

    class DefaultContainerConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName name, IBinder service) {
            if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected");
            IMediaContainerService imcs =
                IMediaContainerService.Stub.asInterface(service);
            mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
        }

        public void onServiceDisconnected(ComponentName name) {
            if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceDisconnected");
        }
    };
我们看下MCS_BOUND消息是怎么处理的,如下:

                case MCS_BOUND: {
                    if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
                    if (msg.obj != null) {
                        mContainerService = (IMediaContainerService) msg.obj;
                    }
                    if (mContainerService == null) {//如果DefaultContainerService没有连接成功
                        // Something seriously wrong. Bail out
                        Slog.e(TAG, "Cannot bind to media container service");
                        for (HandlerParams params : mPendingInstalls) {
                            // Indicate service bind error
                            params.serviceError();//通过参数中带的回调接口通知调用者出错了
                        }
                        mPendingInstalls.clear();
                    } else if (mPendingInstalls.size() > 0) {
                        HandlerParams params = mPendingInstalls.get(0);
                        if (params != null) {
                            if (params.startCopy()) {//执行安装
                                // We are done...  look for more work or to
                                // go idle.
                                if (DEBUG_SD_INSTALL) Log.i(TAG,
                                        "Checking for more work or unbind...");
                                // Delete pending install
                                if (mPendingInstalls.size() > 0) {
                                    mPendingInstalls.remove(0);//工作完成,删除第一项
                                }
                                if (mPendingInstalls.size() == 0) {
                                    if (mBound) {//如果没有安装信息了,发送延时10秒的MCS_UNBIND消息
                                        if (DEBUG_SD_INSTALL) Log.
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值